import importlib
import os
import shutil
import socket
import sys
import threading
import time
import uuid
from multiprocessing import Process, cpu_count

import psutil as psutil
import yaml
from flask import Flask, request, abort
from gevent import monkey
from gevent.pywsgi import WSGIServer

from error.RoutingOverlap import RoutingOverlap
from httpRequest import requestPacket
from utils.tool.log import toolLog
from utils.logRPC import logRPC


class server:
    def __init__(self, config: dict):
        monkey.patch_all()
        self.app = Flask(__name__)
        self.multiverse = None
        self.logProcess = None
        self.logProcessPID = None
        self.logProcessName = uuid.uuid4().__str__()[0:32]
        self.config = config
        self.isStop = False
        self.dissolution = False
        self.plugins = []
        self.interlayer = []
        self.interlayerLen = 0
        self.counter = 0
        self.processPool = []
        self.urlList = {}
        # 启动日志进程
        p = Process(target=logRPC, name=self.logProcessName)
        p.start()
        # 等待启动
        while not p.is_alive():
            pass
        # 等待socket启动
        time.sleep(1)
        self.logProcessPID = p.pid

        self.logProcess = p
        # 输出日志
        toolLog.info("正在创建服务端实例")
        # 硬件检查
        toolLog.info("开始进行硬件环境检查")
        if sys.platform.startswith('linux'):
            if size := int(self.config.get("disk_warning_line")) > (
                    shutil.disk_usage("/")[2] / 3072):
                toolLog.warn(f"系统磁盘空间不足 {size}GB 但依然可以运行")
        elif sys.platform.startswith('win'):
            if size := int(self.config.get("disk_warning_line")) > (
                    shutil.disk_usage(os.getcwd().split("\\")[0])[2] / 3072):
                toolLog.warn(f"系统磁盘空间不足 {size}GB 但依然可以运行")
        # 反射获取所有路由
        for i in os.listdir("./plugin"):
            Main = importlib.import_module(f".Main", package=f"plugin.{i}")
            if not hasattr(Main, "Main"):
                toolLog.error(ModuleNotFoundError(f"plugin/{i} 下不存在插件入口类"))
            else:
                # 实例化插件
                route = Main.Main()
                self.plugins.append(route)  # 并入插件列表

                f = open(f"./plugin/{i}/plugin.yml", 'r', encoding="utf-8")
                plugin_config = yaml.safe_load(f)
                f.close()
                toolLog.info(
                    f"成功实例化插件 插件名称:{plugin_config['name']} Url:{plugin_config['url']} 版本: {plugin_config['version']}")

                if plugin_config["url"] == "interlayer":  # 中间层
                    self.interlayer.append(route)
                    if not self.interlayerLen:
                        self.interlayerLen = 1

                if plugin_config["url"] in self.urlList.keys():
                    toolLog.error(RoutingOverlap(f"Routing overlap occurs when {i}"))
                else:
                    self.urlList[plugin_config["url"]] = route

        # 组建路由映射
        @self.app.before_request
        def receive():
            self.counter += 1
            if self.isStop or self.dissolution:
                abort(503)
            elif request.method in ["GET", "POST"] == False:
                abort(405)
            else:
                # 正常进入处理逻辑
                ask = requestPacket(request)
                if self.interlayerLen:
                    try:
                        for i in self.interlayer:
                            i.run(ask)
                    except Exception as e:
                        toolLog.error(e)
                func = self.urlList.get(request.path)  # 从表中取出路由映射
                if func is not None and ask.isTransmit():
                    try:
                        return func.run(ask), ask.getCode()
                    except Exception as e:
                        toolLog.error(e)
                        abort(500)
                else:
                    abort(404)

        # noinspection PyUnusedLocal
        @self.app.teardown_request
        def teardown_request(code):
            self.counter -= 1

    def run(self, host, port, debug=True):
        if sys.platform.startswith('linux'):
            toolLog.info(
                f"服务端实例以 debug:{debug}模式启动 预计产生 {cpu_count()}个进程 {(cpu_count() * 2 + 1) * cpu_count()}个协程")
        else:
            toolLog.info(f"服务端实例以 debug:{debug}模式启动 预计产生 1个进程 {cpu_count() * 2 + 1}个线程")
        if debug:
            self.app.run(host=host, port=port, debug=True)
        else:
            self.app.logger.disabled = True
            self.app.debug = True
            multiverse = WSGIServer((host, port), self.app, log=None)
            self.multiverse = multiverse
            multiverse.start()

            # noinspection PyProtectedMember
            def server_forever():
                multiverse.start_accepting()
                multiverse._stop_event.wait()

            # noinspection PyShadowingNames
            def super_server_forever():
                # 待优化
                server_forever()

            # noinspection PyProtectedMember
            if sys.platform.startswith('linux'):
                for i in range(cpu_count() + 1):
                    p = Process(target=super_server_forever)
                    p.start()
                    self.processPool.append(p)
            elif sys.platform.startswith('win'):
                for i in range(cpu_count() * 2 + 1):
                    threading.Thread(target=server_forever).start()
            else:
                toolLog.Critical(e := OSError("Unrecognized operating system"))
                raise e

    def getApp(self):
        return self.app

    def close(self):
        self.isStop = True
        threading.Thread(target=self._waitExit_).start()

    def _waitExit_(self):
        while True:
            if self.counter != 0:
                time.sleep(1)
            else:
                for i in self.plugins:
                    try:
                        if hasattr(i, "onDisable"):
                            i.onDisable()
                    except Exception as e:
                        toolLog.error(e)
                # 关闭日志进程
                udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
                udp_socket.sendto("close".encode('utf-8'), ('127.0.0.1', 5656))
                udp_socket.close()

                if sys.platform.startswith('linux'):
                    while psutil.Process(self.logProcessPID).name() == self.logProcessName and psutil.pid_exists(
                            self.logProcessPID): time.sleep(1)
                    for i in self.processPool:
                        i.kill()
                    psutil.Process(os.getpid()).kill()
                else:
                    while self.logProcess.is_alive():
                        time.sleep(1)
                    psutil.Process(os.getpid()).kill()
